home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / var / lib / python-support / python2.6 / gnome_sudoku / gsudoku.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-04-20  |  52.6 KB  |  1,516 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. import gtk
  5. import cairo
  6. import pango
  7. import gobject
  8. import colors
  9. import math
  10. import random
  11. from simple_debug import simple_debug
  12. from gettext import gettext as _
  13. import sudoku
  14. TRACKER_COLORS = [ []([ x / 255 for x in cols ]) for cols in [
  15.     (32, 74, 135),
  16.     (78, 154, 6),
  17.     (206, 92, 0),
  18.     (143, 89, 2),
  19.     (92, 53, 102),
  20.     (85, 87, 83),
  21.     (196, 160, 0)] ]
  22.  
  23. def gtkcolor_to_rgb(c):
  24.     return (c.red / float(65536), c.green / float(65536), c.blue / float(65536))
  25.  
  26.  
  27. def overlay(color_1, color_2, method = 1):
  28.     return (color_1[0] + color_2[0] * method, color_1[1] + color_2[1] * method, color_1[2] + color_2[2] * method)
  29.  
  30. ERROR_HIGHLIGHT_COLOR = (1, 0, 0)
  31. BASE_SIZE = 35
  32. BASE_FONT_SIZE = pango.SCALE * 13
  33. NOTE_FONT_SIZE = pango.SCALE * 6
  34. BORDER_WIDTH = 9
  35. BORDER_LINE_WIDTH = 4
  36. LITTLE_LINE_WIDTH = 0.25
  37. NORMAL_LINE_WIDTH = 1
  38. SPACING_FACTOR = 40
  39. SMALL_TO_BIG_FACTOR = 3.5
  40.  
  41. class NumberSelector(gtk.EventBox):
  42.     __gsignals__ = {
  43.         'changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) }
  44.     
  45.     def __init__(self, default = None, upper = 9):
  46.         self.value = default
  47.         gtk.EventBox.__init__(self)
  48.         self.table = gtk.Table()
  49.         self.add(self.table)
  50.         side = int(math.sqrt(upper))
  51.         n = 1
  52.         for y in range(side):
  53.             for x in range(side):
  54.                 b = gtk.Button()
  55.                 l = gtk.Label()
  56.                 if n == self.value:
  57.                     l.set_markup('<b><span size="x-small">%s</span></b>' % n)
  58.                 else:
  59.                     l.set_markup('<span size="x-small">%s</span>' % n)
  60.                 b.add(l)
  61.                 b.set_relief(gtk.RELIEF_HALF)
  62.                 l = b.get_children()[0]
  63.                 b.set_border_width(0)
  64.                 l.set_padding(0, 0)
  65.                 l.get_alignment()
  66.                 b.connect('clicked', self.number_clicked, n)
  67.                 self.table.attach(b, x, x + 1, y, y + 1)
  68.                 n += 1
  69.             
  70.         
  71.         if self.value:
  72.             db = gtk.Button()
  73.             l = gtk.Label()
  74.             l.set_markup_with_mnemonic('<span size="x-small">' + _('_Clear') + '</span>')
  75.             db.add(l)
  76.             l.show()
  77.             db.connect('clicked', self.number_clicked, 0)
  78.             self.table.attach(db, 0, side, y + 1, y + 2)
  79.         
  80.         self.connect('key-release-event', self.key_press_cb)
  81.         self.show_all()
  82.  
  83.     
  84.     def key_press_cb(self, w, e):
  85.         txt = gtk.gdk.keyval_name(e.keyval)
  86.         if txt == 'Escape':
  87.             self.emit('changed')
  88.         elif txt in ('0', 'Delete', 'BackSpace'):
  89.             self.value = None
  90.             self.emit('changed')
  91.         else:
  92.             
  93.             try:
  94.                 self.value = int(txt)
  95.             except:
  96.                 print "Can't make sense of %s" % txt
  97.  
  98.             self.emit('changed')
  99.  
  100.     
  101.     def number_clicked(self, button, n):
  102.         self.value = n
  103.         self.emit('changed')
  104.  
  105.     
  106.     def get_value(self):
  107.         return self.value
  108.  
  109.     
  110.     def set_value(self, n):
  111.         self.value = n
  112.  
  113.  
  114.  
  115. class NumberBox(gtk.Widget):
  116.     text = ''
  117.     top_note_text = ''
  118.     bottom_note_text = ''
  119.     read_only = False
  120.     read_only_hidden = False
  121.     _layout = None
  122.     _top_note_layout = None
  123.     _bottom_note_layout = None
  124.     text_color = None
  125.     highlight_color = None
  126.     custom_background_color = None
  127.     __gsignals__ = {
  128.         'value-about-to-change': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
  129.         'changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
  130.         'undo-change': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
  131.         'notes-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) }
  132.     base_state = gtk.STATE_NORMAL
  133.     number_picker_mode = False
  134.     draw_boxes = False
  135.     
  136.     def __init__(self, upper = 9, text = ''):
  137.         gtk.Widget.__init__(self)
  138.         self.upper = upper
  139.         self.font = self.style.font_desc
  140.         self.font.set_size(BASE_FONT_SIZE)
  141.         self.note_font = self.font.copy()
  142.         self.note_font.set_size(NOTE_FONT_SIZE)
  143.         self.set_property('can-focus', True)
  144.         self.set_property('events', gtk.gdk.ALL_EVENTS_MASK)
  145.         self.connect('button-press-event', self.button_press_cb)
  146.         self.connect('key-release-event', self.key_press_cb)
  147.         self.connect('enter-notify-event', self.pointer_enter_cb)
  148.         self.connect('leave-notify-event', self.pointer_leave_cb)
  149.         self.connect('focus-in-event', self.focus_in_cb)
  150.         self.connect('focus-out-event', self.focus_out_cb)
  151.         self.connect('motion-notify-event', self.motion_notify_cb)
  152.         self.set_text(text)
  153.  
  154.     
  155.     def pointer_enter_cb(self, *args):
  156.         if not self.is_focus():
  157.             self.set_state(gtk.STATE_PRELIGHT)
  158.         
  159.  
  160.     
  161.     def pointer_leave_cb(self, *args):
  162.         self.set_state(self.base_state)
  163.         self._toggle_box_drawing_(False)
  164.  
  165.     
  166.     def focus_in_cb(self, *args):
  167.         self.set_state(gtk.STATE_SELECTED)
  168.         self.base_state = gtk.STATE_SELECTED
  169.  
  170.     
  171.     def focus_out_cb(self, *args):
  172.         self.set_state(gtk.STATE_NORMAL)
  173.         self.base_state = gtk.STATE_NORMAL
  174.         self.number_picker_mode = False
  175.         if hasattr(self, 'npicker') and self.npicker:
  176.             self.npicker.destroy()
  177.             self.npicker = None
  178.         
  179.  
  180.     
  181.     def motion_notify_cb(self, *args):
  182.         if self.is_focus():
  183.             self._toggle_box_drawing_(True)
  184.         else:
  185.             self._toggle_box_drawing_(False)
  186.  
  187.     
  188.     def _toggle_box_drawing_(self, val):
  189.         if val and not (self.draw_boxes):
  190.             self.draw_boxes = True
  191.             self.queue_draw()
  192.         
  193.         if not val and self.draw_boxes:
  194.             self.draw_boxes = False
  195.             self.queue_draw()
  196.         
  197.  
  198.     
  199.     def button_press_cb(self, w, e):
  200.         if self.read_only:
  201.             return None
  202.         if e.type == gtk.gdk._2BUTTON_PRESS:
  203.             return None
  204.         if self.is_focus():
  205.             (x, y) = e.get_coords()
  206.             alloc = self.get_allocation()
  207.             my_w = alloc.width
  208.             my_h = alloc.height
  209.             if self.number_picker_mode:
  210.                 xperc = float(x) / my_w
  211.                 yperc = float(y) / my_h
  212.                 if xperc > 0.75:
  213.                     self.set_text_interactive('')
  214.                     self.number_picker_mode = False
  215.                     self.queue_draw()
  216.                     return None
  217.                 if xperc > 0.5:
  218.                     xval = 3
  219.                 elif xperc > 0.25:
  220.                     xval = 2
  221.                 else:
  222.                     xval = 1
  223.                 if yperc > 0.66:
  224.                     yval = 6
  225.                 elif yperc > 0.33:
  226.                     yval = 3
  227.                 else:
  228.                     yval = 0
  229.                 self.number_picker_mode = False
  230.                 self.set_text_interactive('')
  231.                 self.set_text_interactive(str(xval + yval))
  232.             else:
  233.                 border_height = float(BORDER_WIDTH) / BASE_SIZE
  234.                 if float(y) / my_h < border_height:
  235.                     self.show_note_editor(top = True)
  236.                 elif float(y) / my_h > 1 - border_height:
  237.                     self.show_note_editor(top = False)
  238.                 elif hasattr(self, 'npicker') and self.npicker:
  239.                     self.npicker.destroy()
  240.                     self.npicker = None
  241.                 
  242.                 self.show_number_picker()
  243.         else:
  244.             self.grab_focus()
  245.  
  246.     
  247.     def key_press_cb(self, w, e):
  248.         if self.read_only:
  249.             return None
  250.         txt = gtk.gdk.keyval_name(e.keyval)
  251.         if type(txt) == type(None):
  252.             return None
  253.         txt = txt.replace('KP_', '')
  254.         if self.get_text() == txt:
  255.             return None
  256.         if txt in ('0', 'Delete', 'BackSpace'):
  257.             self.set_text_interactive('')
  258.         elif txt in ('n', 'N'):
  259.             self.show_note_editor(top = True)
  260.         elif txt in ('m', 'M'):
  261.             self.show_note_editor(top = False)
  262.         elif [] in [ str(n) for n in range(1, self.upper + 1) ]:
  263.             self.set_text_interactive(txt)
  264.         
  265.  
  266.     
  267.     def note_changed_cb(self, w, top = False):
  268.         if top:
  269.             self.set_note_text_interactive(top_text = w.get_text())
  270.         else:
  271.             self.set_note_text_interactive(bottom_text = w.get_text())
  272.  
  273.     
  274.     def show_note_editor(self, top = True):
  275.         alloc = self.get_allocation()
  276.         w = gtk.Window()
  277.         w.set_property('skip-pager-hint', True)
  278.         w.set_property('skip-taskbar-hint', True)
  279.         w.set_decorated(False)
  280.         w.set_position(gtk.WIN_POS_MOUSE)
  281.         w.set_size_request(alloc.width, alloc.height / 2)
  282.         f = gtk.Frame()
  283.         e = gtk.Entry()
  284.         f.add(e)
  285.         if top:
  286.             e.set_text(self.top_note_text)
  287.         else:
  288.             e.set_text(self.bottom_note_text)
  289.         w.add(f)
  290.         e.connect('changed', self.note_changed_cb, top)
  291.         e.connect('focus-out-event', (lambda e, ev, w: w.destroy()), w)
  292.         e.connect('activate', (lambda e, w: w.destroy()), w)
  293.         (x, y) = self.window.get_origin()
  294.         if top:
  295.             w.move(x, y)
  296.         else:
  297.             w.move(x, y + int(alloc.height * 0.6))
  298.         w.show_all()
  299.         e.grab_focus()
  300.  
  301.     
  302.     def number_changed_cb(self, ns, w):
  303.         w.destroy()
  304.         self.set_text_interactive('')
  305.         newval = ns.get_value()
  306.         if newval:
  307.             self.set_text_interactive(str(newval))
  308.         
  309.  
  310.     
  311.     def show_number_picker(self):
  312.         w = gtk.Window(type = gtk.WINDOW_POPUP)
  313.         ns = NumberSelector(upper = self.upper, default = self.get_value())
  314.         ns.connect('changed', self.number_changed_cb, w)
  315.         w.grab_focus()
  316.         w.add(ns)
  317.         r = w.get_allocation()
  318.         my_origin = self.window.get_origin()
  319.         (x, y) = self.window.get_size()
  320.         (popupx, popupy) = w.get_size()
  321.         overlapx = popupx - x
  322.         overlapy = popupy - y
  323.         w.move(my_origin[0] - overlapx / 2, my_origin[1] - overlapy / 2)
  324.         w.show()
  325.         self.npicker = w
  326.  
  327.     
  328.     def set_text_interactive(self, text):
  329.         self.emit('value-about-to-change')
  330.         self.set_text(text)
  331.         self.queue_draw()
  332.         self.emit('changed')
  333.  
  334.     
  335.     def set_font(self, font):
  336.         if type(font) == str:
  337.             font = pango.FontDescription(font)
  338.         
  339.         self.font = font
  340.         if self.text:
  341.             self.set_text(self.text)
  342.         
  343.         self.queue_draw()
  344.  
  345.     
  346.     def set_note_font(self, font):
  347.         if type(font) == str:
  348.             font = pango.FontDescription(font)
  349.         
  350.         self.note_font = font
  351.         if self.top_note_text or self.bottom_note_text:
  352.             self.set_note_text(self.top_note_text, self.bottom_note_text)
  353.         
  354.         self.queue_draw()
  355.  
  356.     
  357.     def set_text(self, text):
  358.         self.text = text
  359.         self._layout = self.create_pango_layout(text)
  360.         self._layout.set_font_description(self.font)
  361.  
  362.     
  363.     def set_notes(self, notes):
  364.         '''Hackish method to allow easy use of Undo API.
  365.  
  366.         Undo API requires a set method that is called with one
  367.         argument (the result of a get method)'''
  368.         self.set_note_text(top_text = notes[0], bottom_text = notes[1])
  369.         self.queue_draw()
  370.  
  371.     
  372.     def set_note_text(self, top_text = None, bottom_text = None):
  373.         if top_text is not None:
  374.             self.top_note_text = top_text
  375.             self._top_note_layout = self.create_pango_layout(top_text)
  376.             self._top_note_layout.set_font_description(self.note_font)
  377.         
  378.         if bottom_text is not None:
  379.             self.bottom_note_text = bottom_text
  380.             self._bottom_note_layout = self.create_pango_layout(bottom_text)
  381.             self._bottom_note_layout.set_font_description(self.note_font)
  382.         
  383.         self.queue_draw()
  384.  
  385.     
  386.     def set_note_text_interactive(self, *args, **kwargs):
  387.         self.emit('value-about-to-change')
  388.         self.set_note_text(*args, **kwargs)
  389.         self.emit('notes-changed')
  390.  
  391.     
  392.     def do_realize(self):
  393.         self.set_flags(self.flags() | gtk.REALIZED)
  394.         self.window = gtk.gdk.Window(self.get_parent_window(), width = self.allocation.width, height = self.allocation.height, window_type = gtk.gdk.WINDOW_CHILD, wclass = gtk.gdk.INPUT_OUTPUT, event_mask = self.get_events() | gtk.gdk.EXPOSURE_MASK)
  395.         self.window.set_user_data(self)
  396.         self.style.attach(self.window)
  397.         self.style.set_background(self.window, gtk.STATE_NORMAL)
  398.         self.window.move_resize(*self.allocation)
  399.  
  400.     
  401.     def do_unrealize(self):
  402.         self.window.set_user_data(None)
  403.  
  404.     
  405.     def do_size_request(self, requisition):
  406.         (width, height) = self._layout.get_size()
  407.         if width > height:
  408.             side = width / pango.SCALE
  409.         else:
  410.             side = height / pango.SCALE
  411.         requisition.width = side
  412.         requisition.height = side
  413.  
  414.     
  415.     def do_size_allocate(self, allocation):
  416.         self.allocation = allocation
  417.         if self.flags() & gtk.REALIZED:
  418.             self.window.move_resize(*allocation)
  419.         
  420.  
  421.     
  422.     def do_expose_event(self, event):
  423.         (x, y, w, h) = self.allocation
  424.         cr = self.window.cairo_create()
  425.         if h < w:
  426.             scale = h / float(BASE_SIZE)
  427.         else:
  428.             scale = w / float(BASE_SIZE)
  429.         cr.scale(scale, scale)
  430.         self.draw_background_color(cr)
  431.         if self.number_picker_mode:
  432.             self.draw_numbers(cr)
  433.             return None
  434.         if self.is_focus():
  435.             self.draw_highlight_box(cr)
  436.         
  437.         self.draw_normal_box(cr)
  438.         self.draw_text(cr)
  439.         if self.draw_boxes and self.is_focus():
  440.             self.draw_note_area_highlight_box(cr)
  441.         
  442.  
  443.     
  444.     def draw_background_color(self, cr):
  445.         if self.read_only:
  446.             if self.custom_background_color:
  447.                 (r, g, b) = self.custom_background_color
  448.                 cr.set_source_rgb(r * 0.6, g * 0.6, b * 0.6)
  449.             else:
  450.                 cr.set_source_color(self.style.base[gtk.STATE_INSENSITIVE])
  451.         elif self.is_focus():
  452.             cr.set_source_color(self.style.base[gtk.STATE_SELECTED])
  453.         elif self.custom_background_color:
  454.             cr.set_source_rgb(*self.custom_background_color)
  455.         else:
  456.             cr.set_source_color(self.style.base[self.state])
  457.         cr.rectangle(0, 0, BASE_SIZE, BASE_SIZE)
  458.         cr.fill()
  459.  
  460.     
  461.     def draw_normal_box(self, cr):
  462.         state = self.state
  463.         if state == gtk.STATE_SELECTED:
  464.             state = gtk.STATE_NORMAL
  465.         
  466.         cr.set_source_color(self.style.mid[state])
  467.         cr.rectangle(NORMAL_LINE_WIDTH * 0.5, NORMAL_LINE_WIDTH * 0.5, BASE_SIZE - NORMAL_LINE_WIDTH, BASE_SIZE - NORMAL_LINE_WIDTH)
  468.         cr.set_line_width(NORMAL_LINE_WIDTH)
  469.         cr.set_line_join(cairo.LINE_JOIN_ROUND)
  470.         cr.stroke()
  471.         cr.set_source_color(self.style.dark[state])
  472.         cr.rectangle(NORMAL_LINE_WIDTH * 0.25, NORMAL_LINE_WIDTH * 0.25, BASE_SIZE - NORMAL_LINE_WIDTH * 0.5, BASE_SIZE - NORMAL_LINE_WIDTH * 0.5)
  473.         cr.set_line_width(NORMAL_LINE_WIDTH * 0.5)
  474.         cr.set_line_join(cairo.LINE_JOIN_MITER)
  475.         cr.stroke()
  476.  
  477.     
  478.     def draw_highlight_box(self, cr):
  479.         cr.set_source_color(self.style.base[gtk.STATE_SELECTED])
  480.         cr.rectangle(BORDER_LINE_WIDTH * 0.5, BORDER_LINE_WIDTH * 0.5, BASE_SIZE - BORDER_LINE_WIDTH, BASE_SIZE - BORDER_LINE_WIDTH)
  481.         cr.set_line_width(BORDER_LINE_WIDTH)
  482.         cr.set_line_join(cairo.LINE_JOIN_ROUND)
  483.         cr.stroke()
  484.  
  485.     
  486.     def draw_note_area_highlight_box(self, cr):
  487.         cr.set_source_color(self.style.mid[self.state])
  488.         cr.set_line_width(NORMAL_LINE_WIDTH)
  489.         cr.set_line_join(cairo.LINE_JOIN_ROUND)
  490.         cr.rectangle(NORMAL_LINE_WIDTH * 0.5, NORMAL_LINE_WIDTH * 0.5, BASE_SIZE - NORMAL_LINE_WIDTH, BORDER_WIDTH - NORMAL_LINE_WIDTH)
  491.         cr.stroke()
  492.         cr.rectangle(NORMAL_LINE_WIDTH * 0.5, BASE_SIZE - BORDER_WIDTH - NORMAL_LINE_WIDTH * 0.5, BASE_SIZE - NORMAL_LINE_WIDTH, BASE_SIZE - NORMAL_LINE_WIDTH)
  493.         cr.stroke()
  494.  
  495.     
  496.     def draw_text(self, cr):
  497.         if self.text_color:
  498.             cr.set_source_rgb(*self.text_color)
  499.         elif self.read_only:
  500.             cr.set_source_color(self.style.text[gtk.STATE_NORMAL])
  501.         else:
  502.             cr.set_source_color(self.style.text[self.state])
  503.         if self._layout:
  504.             (fontw, fonth) = self._layout.get_pixel_size()
  505.             cr.move_to(BASE_SIZE / 2 - fontw / 2, BASE_SIZE / 2 - fonth / 2)
  506.             cr.update_layout(self._layout)
  507.             cr.show_layout(self._layout)
  508.         
  509.         cr.set_source_color(self.style.text[self.state])
  510.         if self._top_note_layout:
  511.             (fontw, fonth) = self._top_note_layout.get_pixel_size()
  512.             cr.move_to(NORMAL_LINE_WIDTH, 0)
  513.             cr.update_layout(self._top_note_layout)
  514.             cr.show_layout(self._top_note_layout)
  515.         
  516.         if self._bottom_note_layout:
  517.             (fontw, fonth) = self._bottom_note_layout.get_pixel_size()
  518.             cr.move_to(NORMAL_LINE_WIDTH, BASE_SIZE - fonth)
  519.             cr.update_layout(self._bottom_note_layout)
  520.             cr.show_layout(self._bottom_note_layout)
  521.         
  522.  
  523.     
  524.     def draw_numbers(self, cr):
  525.         if not hasattr(self, 'number_text'):
  526.             self.small_digit_height = 1
  527.             self.small_digit_width = 1
  528.             self.number_text = []
  529.             for n in range(self.upper):
  530.                 if type(n) == int:
  531.                     n = str(n + 1)
  532.                 
  533.                 txt = self.create_pango_layout(n)
  534.                 txt.set_font_description(self.note_font)
  535.                 if not hasattr(self, 'bold_note_font'):
  536.                     self.bold_note_font = self.note_font.copy()
  537.                     self.bold_note_font.set_weight(pango.WEIGHT_BOLD)
  538.                 
  539.                 bold_txt = self.create_pango_layout(n)
  540.                 bold_txt.set_font_description(self.bold_note_font)
  541.                 self.number_text.append((txt, bold_txt))
  542.                 (w, h) = bold_txt.get_pixel_size()
  543.                 if w > self.small_digit_width:
  544.                     self.small_digit_width = w * 1.2
  545.                 
  546.                 if h > self.small_digit_height:
  547.                     self.small_digit_height = h
  548.                     continue
  549.             
  550.         
  551.         val = self.get_value()
  552.         cols = (BASE_SIZE - NORMAL_LINE_WIDTH * 2) / self.small_digit_width
  553.         rows = (BASE_SIZE - NORMAL_LINE_WIDTH * 2) / self.small_digit_height
  554.         cols = 3
  555.         rows = 3
  556.         row_size = BASE_SIZE / rows
  557.         col_size = BASE_SIZE / cols
  558.         n = 0
  559.         for y in range(rows):
  560.             for x in range(cols):
  561.                 if y < 2 and x > 2:
  562.                     continue
  563.                 
  564.                 if n >= len(self.number_text):
  565.                     break
  566.                 
  567.                 txt = self.number_text[n]
  568.                 if val == n + 1:
  569.                     layout = txt[1]
  570.                 else:
  571.                     layout = txt[0]
  572.                 (w, h) = layout.get_pixel_size()
  573.                 xpadding = (col_size - w) / 2
  574.                 ypadding = (row_size - h) / 2
  575.                 cr.set_source_color(gtk.gdk.Color(65536, 0, 0))
  576.                 cr.set_line_width(LITTLE_LINE_WIDTH)
  577.                 cr.rectangle(LITTLE_LINE_WIDTH * 0.5 + NORMAL_LINE_WIDTH * 0.5 + x * col_size, LITTLE_LINE_WIDTH * 0.5 + NORMAL_LINE_WIDTH * 0.5 + y * row_size, LITTLE_LINE_WIDTH * 0.5 + NORMAL_LINE_WIDTH * 0.5 + (x + 1) * col_size, LITTLE_LINE_WIDTH * 0.5 + NORMAL_LINE_WIDTH * 0.5 + (y + 1) * row_size)
  578.                 cr.stroke()
  579.                 cr.set_source_color(self.style.text[self.state])
  580.                 cr.move_to(NORMAL_LINE_WIDTH + x * col_size + xpadding, NORMAL_LINE_WIDTH + y * row_size + ypadding)
  581.                 cr.update_layout(layout)
  582.                 cr.show_layout(layout)
  583.                 n += 1
  584.             
  585.         
  586.  
  587.     
  588.     def set_text_color(self, color):
  589.         self.text_color = color
  590.         self.queue_draw()
  591.  
  592.     
  593.     def set_background_color(self, color):
  594.         self.custom_background_color = color
  595.         self.queue_draw()
  596.  
  597.     
  598.     def hide_notes(self):
  599.         pass
  600.  
  601.     
  602.     def show_notes(self):
  603.         pass
  604.  
  605.     
  606.     def set_value_from_undo(self, v):
  607.         self.set_value(v)
  608.         self.emit('undo_change')
  609.  
  610.     
  611.     def set_value(self, v):
  612.         if v < v:
  613.             pass
  614.         elif v <= self.upper:
  615.             self.set_text(str(v))
  616.         else:
  617.             self.set_text('')
  618.         self.queue_draw()
  619.  
  620.     
  621.     def get_value(self):
  622.         
  623.         try:
  624.             return int(self.text)
  625.         except:
  626.             return None
  627.  
  628.  
  629.     
  630.     def get_text(self):
  631.         return self.text
  632.  
  633.     
  634.     def get_note_text(self):
  635.         return (self.top_note_text, self.bottom_note_text)
  636.  
  637.  
  638.  
  639. class SudokuNumberBox(NumberBox):
  640.     normal_color = None
  641.     highlight_color = ERROR_HIGHLIGHT_COLOR
  642.     
  643.     def set_color(self, color):
  644.         self.normal_color = color
  645.         self.set_text_color(self.normal_color)
  646.  
  647.     
  648.     def unset_color(self):
  649.         self.set_color(None)
  650.  
  651.     
  652.     def set_error_highlight(self, val):
  653.         if val:
  654.             self.set_text_color((1, 0, 0))
  655.         else:
  656.             self.set_text_color(self.normal_color)
  657.  
  658.     
  659.     def set_read_only(self, val):
  660.         self.read_only = val
  661.         if not hasattr(self, 'bold_font'):
  662.             self.normal_font = self.font
  663.             self.bold_font = self.font.copy()
  664.             self.bold_font.set_weight(pango.WEIGHT_BOLD)
  665.         
  666.         if self.read_only:
  667.             self.set_font(self.bold_font)
  668.         else:
  669.             self.set_font(self.normal_font)
  670.         self.queue_draw()
  671.  
  672.     
  673.     def set_impossible(self, val):
  674.         if val:
  675.             self.set_text('X')
  676.         else:
  677.             self.set_text('')
  678.  
  679.  
  680. gobject.type_register(NumberBox)
  681.  
  682. class SudokuNumberGrid(gtk.AspectFrame):
  683.     
  684.     def __init__(self, group_size = 9):
  685.         self.table = gtk.Table(rows = group_size, columns = group_size, homogeneous = True)
  686.         self.group_size = group_size
  687.         self.__entries__ = { }
  688.         for x in range(self.group_size):
  689.             for y in range(self.group_size):
  690.                 e = SudokuNumberBox(upper = self.group_size)
  691.                 e.x = x
  692.                 e.y = y
  693.                 self.table.attach(e, x, x + 1, y, y + 1)
  694.                 self.__entries__[(x, y)] = e
  695.             
  696.         
  697.         gtk.AspectFrame.__init__(self, obey_child = False)
  698.         self.set_shadow_type(gtk.SHADOW_NONE)
  699.         self.eb = gtk.EventBox()
  700.         self.eb.add(self.table)
  701.         self.add(self.eb)
  702.         self.connect('size-allocate', self.allocate_cb)
  703.         self.show_all()
  704.  
  705.     
  706.     def allocate_cb(self, w, rect):
  707.         if rect.width > rect.height:
  708.             side = rect.height
  709.         else:
  710.             side = rect.width
  711.         spacing = float(side) / (self.group_size * SPACING_FACTOR)
  712.         if spacing == 0:
  713.             spacing = 1
  714.         
  715.         if hasattr(self, 'small_spacing') and spacing == self.small_spacing:
  716.             return None
  717.         self.change_spacing(spacing)
  718.  
  719.     
  720.     def change_spacing(self, small_spacing):
  721.         self.small_spacing = small_spacing
  722.         self.big_spacing = int(small_spacing * SMALL_TO_BIG_FACTOR)
  723.         self.table.set_row_spacings(int(small_spacing))
  724.         self.table.set_col_spacings(int(small_spacing))
  725.         box_side = int(math.sqrt(self.group_size))
  726.         for n in range(1, box_side):
  727.             self.table.set_row_spacing(box_side * n - 1, self.big_spacing)
  728.             self.table.set_col_spacing(box_side * n - 1, self.big_spacing)
  729.         
  730.         self.table.set_border_width(self.big_spacing)
  731.  
  732.     
  733.     def get_focused_entry(self):
  734.         for e in self.__entries__.values():
  735.             if e.is_focus():
  736.                 return e
  737.         
  738.  
  739.     
  740.     def set_bg_color(self, color):
  741.         if type(color) == str:
  742.             
  743.             try:
  744.                 color = gtk.gdk.color_parse(color)
  745.             print 'set_bg_color handed Bad color', color
  746.             return None
  747.  
  748.         
  749.         self.eb.modify_bg(gtk.STATE_NORMAL, color)
  750.         self.eb.modify_base(gtk.STATE_NORMAL, color)
  751.         self.eb.modify_fg(gtk.STATE_NORMAL, color)
  752.         self.table.modify_bg(gtk.STATE_NORMAL, color)
  753.         self.table.modify_base(gtk.STATE_NORMAL, color)
  754.         self.table.modify_fg(gtk.STATE_NORMAL, color)
  755.         for e in self.__entries__.values():
  756.             e.modify_bg(gtk.STATE_NORMAL, color)
  757.         
  758.  
  759.  
  760.  
  761. class ParallelDict(dict):
  762.     '''A handy new sort of dictionary for tracking conflicts.
  763.  
  764.     pd = ParallelDict()
  765.     pd[1] = [2,3,4] # 1 is linked with 2,3 and 4
  766.     pd -> {1:[2,3,4],2:[1],3:[1],4:[1]}
  767.     pd[2] = [1,3,4] # 2 is linked with 3 and 4 as well as 1
  768.     pd -> {1: [2,3,4],2:[3,4],3:[1,2],4:[1,2]}
  769.     Now for the cool part...
  770.     del pd[1]
  771.     pd -> {2: [2,3],3:[2],4:[2]}
  772.     
  773.     Pretty neat, no?
  774.     '''
  775.     
  776.     def __init__(self, *args):
  777.         dict.__init__(self, *args)
  778.  
  779.     
  780.     def __setitem__(self, k, v):
  781.         dict.__setitem__(self, k, set(v))
  782.         for i in v:
  783.             if i == k:
  784.                 continue
  785.             
  786.             if self.has_key(i):
  787.                 self[i].add(k)
  788.                 continue
  789.             dict.__setitem__(self, i, set([
  790.                 k]))
  791.         
  792.  
  793.     
  794.     def __delitem__(self, k):
  795.         v = self[k]
  796.         dict.__delitem__(self, k)
  797.         for i in v:
  798.             if i == k:
  799.                 continue
  800.             
  801.             if self.has_key(i):
  802.                 if k in self[i]:
  803.                     self[i].remove(k)
  804.                 
  805.                 if not self[i]:
  806.                     dict.__delitem__(self, i)
  807.                 
  808.             self[i]
  809.         
  810.  
  811.  
  812.  
  813. class SudokuGameDisplay(SudokuNumberGrid, gobject.GObject):
  814.     __gsignals__ = {
  815.         'focus-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
  816.         'puzzle-finished': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) }
  817.     do_highlight_cells = False
  818.     
  819.     def __init__(self, grid = None, group_size = 9, show_impossible_implications = False):
  820.         group_size = int(group_size)
  821.         self.hints = 0
  822.         self.always_show_hints = False
  823.         self.auto_fills = 0
  824.         self.show_impossible_implications = show_impossible_implications
  825.         self.impossible_hints = 0
  826.         self.impossibilities = []
  827.         self.trackers = { }
  828.         self.__trackers_tracking__ = { }
  829.         self.__colors_used__ = [
  830.             None,
  831.             ERROR_HIGHLIGHT_COLOR]
  832.         gobject.GObject.__init__(self)
  833.         SudokuNumberGrid.__init__(self, group_size = group_size)
  834.         self.setup_grid(grid, group_size)
  835.         for e in self.__entries__.values():
  836.             e.show()
  837.             e.connect('undo-change', self.entry_callback)
  838.             e.connect('changed', self.entry_callback)
  839.             e.connect('focus-in-event', self.focus_callback)
  840.         
  841.         self.connect('focus-changed', self.highlight_cells)
  842.  
  843.     __init__ = simple_debug(__init__)
  844.     
  845.     def focus_callback(self, e, event):
  846.         self.focused = e
  847.         self.emit('focus-changed')
  848.  
  849.     focus_callback = simple_debug(focus_callback)
  850.     
  851.     def get_highlight_colors(self):
  852.         entry = self.__entries__.values()[0]
  853.         default_color = gtkcolor_to_rgb(entry.style.bg[gtk.STATE_SELECTED])
  854.         hsv = colors.rgb_to_hsv(*default_color)
  855.         box_s = hsv[1]
  856.         box_v = hsv[2]
  857.         if box_v < 0.5:
  858.             box_v = box_v * 2
  859.         
  860.         if box_s > 0.75:
  861.             box_s = box_s * 0.5
  862.         else:
  863.             box_s = box_s * 1.5
  864.             if box_s > 1:
  865.                 box_s = 1
  866.             
  867.         self.box_color = colors.hsv_to_rgb(hsv[0], box_s, box_v)
  868.         self.box_and_row_color = colors.rotate_hue_rgb(*self.box_color, **{
  869.             'rotate_by': 0.33 / 2 })
  870.         self.row_color = colors.rotate_hue_rgb(*self.box_color, **{
  871.             'rotate_by': 0.33 })
  872.         self.col_color = colors.rotate_hue_rgb(*self.box_color, **{
  873.             'rotate_by': 0.66 })
  874.         self.box_and_col_color = colors.rotate_hue_rgb(*self.box_color, **{
  875.             'rotate_by': 1 - 0.33 / 2 })
  876.  
  877.     
  878.     def toggle_highlight(self, val):
  879.         self.do_highlight_cells = val
  880.         self.unhighlight_cells()
  881.         if hasattr(self, 'focused') and self.focused:
  882.             self.highlight_cells()
  883.         
  884.  
  885.     
  886.     def unhighlight_cells(self, *args):
  887.         for e in self.__entries__.values():
  888.             e.set_background_color(None)
  889.         
  890.  
  891.     
  892.     def highlight_cells(self, *args):
  893.         if not self.do_highlight_cells:
  894.             return None
  895.         self.unhighlight_cells()
  896.         if not hasattr(self, 'box_color'):
  897.             self.get_highlight_colors()
  898.         
  899.         my_x = self.focused.x
  900.         my_y = self.focused.y
  901.         if not hasattr(self.grid, 'col_coords'):
  902.             return None
  903.         for x, y in self.grid.col_coords[my_x]:
  904.             if (x, y) != (my_x, my_y):
  905.                 self.__entries__[(x, y)].set_background_color(self.col_color)
  906.                 continue
  907.             hasattr(self.grid, 'col_coords')
  908.         
  909.         for x, y in self.grid.row_coords[my_y]:
  910.             if (x, y) != (my_x, my_y):
  911.                 self.__entries__[(x, y)].set_background_color(self.row_color)
  912.                 continue
  913.         
  914.         for x, y in self.grid.box_coords[self.grid.box_by_coords[(my_x, my_y)]]:
  915.             if (x, y) != (my_x, my_y):
  916.                 e = self.__entries__[(x, y)]
  917.                 if x == my_x:
  918.                     e.set_background_color(self.box_and_col_color)
  919.                 elif y == my_y:
  920.                     e.set_background_color(self.box_and_row_color)
  921.                 else:
  922.                     e.set_background_color(self.box_color)
  923.             x == my_x
  924.         
  925.  
  926.     
  927.     def show_hint(self):
  928.         if hasattr(self, 'focused'):
  929.             entry = self.focused
  930.             if entry.read_only or entry.get_text():
  931.                 pass
  932.             else:
  933.                 self.show_hint_for_entry(entry, interactive = True)
  934.         
  935.  
  936.     show_hint = simple_debug(show_hint)
  937.     
  938.     def show_hint_for_entry(self, entry, interactive = False):
  939.         if interactive:
  940.             set_method = entry.set_note_text_interactive
  941.         else:
  942.             set_method = entry.set_note_text
  943.         vals = self.grid.possible_values(entry.x, entry.y)
  944.         vals = list(vals)
  945.         vals.sort()
  946.         if vals:
  947.             []([ str(v) for v in vals ])
  948.             txt = []([ str(v) for v in vals ])
  949.             if txt != entry.get_text():
  950.                 set_method(bottom_text = txt)
  951.                 self.hints += 1
  952.             
  953.         elif not entry.get_text():
  954.             if entry.get_text() != 'X':
  955.                 self.hints += 1
  956.                 set_method(bottom_text = 'X')
  957.             
  958.         else:
  959.             set_method(bottom_text = '')
  960.  
  961.     
  962.     def num_to_str(self, n):
  963.         if n >= 10:
  964.             return SuperNumberEntry.conversions[n]
  965.         return str(n)
  966.  
  967.     num_to_str = simple_debug(num_to_str)
  968.     
  969.     def reset_grid(self):
  970.         '''Reset grid to its original setup.
  971.  
  972.         Return a list of items we removed so that callers can handle
  973.         e.g. Undo properly'''
  974.         removed = []
  975.         for x in range(self.group_size):
  976.             for y in range(self.group_size):
  977.                 if not self.grid.virgin._get_(x, y):
  978.                     val = self.__entries__[(x, y)].get_value()
  979.                     if val:
  980.                         removed.append((x, y, val, self.trackers_for_point(x, y, val)))
  981.                         self.remove(x, y, do_removal = True)
  982.                     
  983.                 val
  984.             
  985.         
  986.         return removed
  987.  
  988.     reset_grid = simple_debug(reset_grid)
  989.     
  990.     def clear_notes(self, clear_args = {
  991.         'top_text': '',
  992.         'bottom_text': '' }):
  993.         '''Remove all notes.'''
  994.         self.removed = []
  995.         for x in range(self.group_size):
  996.             for y in range(self.group_size):
  997.                 e = self.__entries__[(x, y)]
  998.                 (top, bottom) = e.get_note_text()
  999.                 if top or bottom:
  1000.                     self.removed.append((x, y, (top, bottom)))
  1001.                     e.set_note_text(**clear_args)
  1002.                     e.queue_draw()
  1003.                     continue
  1004.             
  1005.         
  1006.         return self.removed
  1007.  
  1008.     
  1009.     def clear_hints(self):
  1010.         self.clear_notes(clear_args = {
  1011.             'bottom_text': '' })
  1012.  
  1013.     
  1014.     def blank_grid(self):
  1015.         for x in range(self.group_size):
  1016.             for y in range(self.group_size):
  1017.                 self.remove(x, y)
  1018.                 e = self.__entries__[(x, y)]
  1019.                 e.set_read_only(False)
  1020.             
  1021.         
  1022.         self.grid = None
  1023.         self.clear_notes()
  1024.  
  1025.     blank_grid = simple_debug(blank_grid)
  1026.     
  1027.     def change_grid(self, grid, group_size):
  1028.         self.auto_fills = 0
  1029.         self.hints = 0
  1030.         self.impossible_hints = 0
  1031.         self.trackers = { }
  1032.         self.__trackers_tracking__ = { }
  1033.         self.__colors_used__ = [
  1034.             None,
  1035.             ERROR_HIGHLIGHT_COLOR]
  1036.         self.blank_grid()
  1037.         self.setup_grid(grid, group_size)
  1038.  
  1039.     change_grid = simple_debug(change_grid)
  1040.     
  1041.     def load_game(self, game):
  1042.         '''Load a game.
  1043.  
  1044.         A game is simply a two lined string where the first line represents our
  1045.         virgin self and line two represents our game-in-progress.
  1046.         '''
  1047.         self.blank_grid()
  1048.         if '\n' in game:
  1049.             (virgin, in_prog) = game.split('\n')
  1050.         else:
  1051.             virgin = game
  1052.             in_prog = ''
  1053.         group_size = int(math.sqrt(len(virgin.split())))
  1054.         self.change_grid(virgin, group_size = group_size)
  1055.         if in_prog:
  1056.             values = [ int(c) for c in in_prog.split() ]
  1057.             for row in range(group_size):
  1058.                 for col in range(group_size):
  1059.                     index = row * 9 + col
  1060.                     if values[index] and not self.grid._get_(col, row):
  1061.                         self.add_value_to_ui(col, row, values[index])
  1062.                         continue
  1063.                     []
  1064.                 
  1065.             
  1066.         
  1067.  
  1068.     load_game = simple_debug(load_game)
  1069.     
  1070.     def setup_grid(self, grid, group_size):
  1071.         self.doing_initial_setup = True
  1072.         self.__error_pairs__ = ParallelDict()
  1073.         if isinstance(grid, sudoku.SudokuGrid):
  1074.             self.grid = sudoku.InteractiveSudoku(grid.grid, group_size = grid.group_size)
  1075.         else:
  1076.             self.grid = sudoku.InteractiveSudoku(grid, group_size = group_size)
  1077.         for x in range(group_size):
  1078.             for y in range(group_size):
  1079.                 val = self.grid._get_(x, y)
  1080.                 if val:
  1081.                     self.add_value(x, y, val)
  1082.                     continue
  1083.             
  1084.         
  1085.         self.doing_initial_setup = False
  1086.  
  1087.     setup_grid = simple_debug(setup_grid)
  1088.     
  1089.     def entry_callback(self, widget, *args):
  1090.         if not widget.get_text():
  1091.             if self.grid and self.grid._get_(widget.x, widget.y):
  1092.                 self.grid.remove(widget.x, widget.y)
  1093.             
  1094.             self.remove(widget.x, widget.y)
  1095.         else:
  1096.             self.entry_validate(widget)
  1097.         if self.show_impossible_implications:
  1098.             self.mark_impossible_implications(widget.x, widget.y)
  1099.         
  1100.         if self.always_show_hints:
  1101.             self.update_all_hints()
  1102.         
  1103.  
  1104.     entry_callback = simple_debug(entry_callback)
  1105.     
  1106.     def update_all_hints(self):
  1107.         for x in range(self.group_size):
  1108.             for y in range(self.group_size):
  1109.                 e = self.__entries__[(x, y)]
  1110.                 if e.read_only:
  1111.                     continue
  1112.                 if e.get_text():
  1113.                     e.set_note_text(bottom_text = '')
  1114.                     continue
  1115.                 self.show_hint_for_entry(e)
  1116.             
  1117.         
  1118.  
  1119.     
  1120.     def entry_validate(self, widget, *args):
  1121.         val = widget.get_value()
  1122.         
  1123.         try:
  1124.             self.add_value(widget.x, widget.y, val)
  1125.             if self.grid.check_for_completeness():
  1126.                 self.emit('puzzle-finished')
  1127.         except sudoku.ConflictError:
  1128.             err = None
  1129.             conflicts = self.grid.find_conflicts(err.x, err.y, err.value)
  1130.             for conflict in conflicts:
  1131.                 widget.set_error_highlight(True)
  1132.                 self.__entries__[conflict].set_error_highlight(True)
  1133.             
  1134.             self.__error_pairs__[(err.x, err.y)] = conflicts
  1135.  
  1136.  
  1137.     entry_validate = simple_debug(entry_validate)
  1138.     
  1139.     def add_value_to_ui(self, x, y, val, trackers = []):
  1140.         '''Add our value back to our grid come hell or high water.
  1141.  
  1142.         We add our value -- if there is an error, we make the value
  1143.         appear as if the user had typed it; i.e. it will show up with
  1144.         error highlighting.'''
  1145.         
  1146.         try:
  1147.             self.add_value(x, y, val, trackers = [])
  1148.         except sudoku.ConflictError:
  1149.             self.entry_validate(self.__entries__[(x, y)])
  1150.  
  1151.  
  1152.     
  1153.     def add_value(self, x, y, val, trackers = []):
  1154.         '''Add value val at position x,y.
  1155.  
  1156.         If tracker is True, we track it with tracker ID tracker.
  1157.  
  1158.         Otherwise, we use any currently tracking trackers to track our addition.
  1159.  
  1160.         Providing the tracker arg is mostly useful for e.g. undo/redo
  1161.         or removed items.
  1162.  
  1163.         To specify NO trackers, use trackers=[-1]
  1164.         '''
  1165.         self.__entries__[(x, y)].set_value(val)
  1166.         if self.doing_initial_setup:
  1167.             self.__entries__[(x, y)].set_read_only(True)
  1168.         
  1169.         if trackers:
  1170.             for tracker in trackers:
  1171.                 if tracker == -1:
  1172.                     pass
  1173.                 
  1174.                 self.__entries__[(x, y)].set_color(self.get_tracker_color(tracker))
  1175.                 self.trackers[tracker].append((x, y, val))
  1176.             
  1177.         elif True in self.__trackers_tracking__.values():
  1178.             for k, v in self.__trackers_tracking__.items():
  1179.                 if v:
  1180.                     self.__entries__[(x, y)].set_color(self.get_tracker_color(k))
  1181.                     self.trackers[k].append((x, y, val))
  1182.                     continue
  1183.             
  1184.         
  1185.         self.grid.add(x, y, val, True)
  1186.         self.__entries__[(x, y)].queue_draw()
  1187.  
  1188.     add_value = simple_debug(add_value)
  1189.     
  1190.     def remove(self, x, y, do_removal = False):
  1191.         '''Remove x,y from our visible grid.
  1192.  
  1193.         If do_removal, remove it from our underlying grid as well.
  1194.         '''
  1195.         e = self.__entries__[(x, y)]
  1196.         if do_removal and self.grid and self.grid._get_(x, y):
  1197.             self.grid.remove(x, y)
  1198.         
  1199.         if self.__error_pairs__.has_key((x, y)):
  1200.             e.set_error_highlight(False)
  1201.             errors_removed = self.__error_pairs__[(x, y)]
  1202.             del self.__error_pairs__[(x, y)]
  1203.             for coord in errors_removed:
  1204.                 if not self.__error_pairs__.has_key(coord):
  1205.                     linked_entry = self.__entries__[coord]
  1206.                     linked_entry.set_error_highlight(False)
  1207.                     if self.grid and not self.grid._get_(linked_entry.x, linked_entry.y):
  1208.                         self.entry_validate(linked_entry)
  1209.                     
  1210.                 not self.grid._get_(linked_entry.x, linked_entry.y)
  1211.             
  1212.         
  1213.         for t in self.trackers_for_point(x, y):
  1214.             remove = []
  1215.             for crumb in self.trackers[t]:
  1216.                 if crumb[0] == x and crumb[1] == y:
  1217.                     remove.append(crumb)
  1218.                     continue
  1219.             
  1220.             for r in remove:
  1221.                 self.trackers[t].remove(r)
  1222.             
  1223.         
  1224.         if e.get_text():
  1225.             e.set_value(0)
  1226.         
  1227.         e.unset_color()
  1228.  
  1229.     remove = simple_debug(remove)
  1230.     
  1231.     def auto_fill(self):
  1232.         changed = self.grid.auto_fill()
  1233.         retval = []
  1234.         for coords, val in changed:
  1235.             self.add_value(coords[0], coords[1], val)
  1236.             retval.append((coords[0], coords[1], val))
  1237.             if self.show_impossible_implications:
  1238.                 self.mark_impossible_implications(*coords)
  1239.                 continue
  1240.         
  1241.         if retval:
  1242.             self.auto_fills += 1
  1243.         
  1244.         if self.grid.check_for_completeness():
  1245.             self.emit('puzzle-finished')
  1246.         
  1247.         return retval
  1248.  
  1249.     auto_fill = simple_debug(auto_fill)
  1250.     
  1251.     def auto_fill_current_entry(self):
  1252.         e = self.get_focused_entry()
  1253.         if not e:
  1254.             return None
  1255.         filled = self.grid.auto_fill_for_xy(e.x, e.y)
  1256.         if filled and filled != -1:
  1257.             e.set_text_interactive('')
  1258.             e.set_text_interactive(str(filled[1]))
  1259.         
  1260.  
  1261.     auto_fill_current_entry = simple_debug(auto_fill_current_entry)
  1262.     
  1263.     def mark_impossible_implications(self, x, y):
  1264.         if not self.grid:
  1265.             return None
  1266.         implications = self.grid.find_impossible_implications(x, y)
  1267.         if implications:
  1268.             for x, y in implications:
  1269.                 self.__entries__[(x, y)].set_impossible(True)
  1270.                 if (x, y) not in self.impossibilities:
  1271.                     self.impossible_hints += 1
  1272.                     continue
  1273.                 self
  1274.             
  1275.         
  1276.         for x, y in self.impossibilities:
  1277.             if (x, y) not in implications:
  1278.                 self.__entries__[(x, y)].set_impossible(False)
  1279.                 continue
  1280.         
  1281.         self.impossibilities = implications
  1282.  
  1283.     mark_impossible_implications = simple_debug(mark_impossible_implications)
  1284.     
  1285.     def create_tracker(self, identifier = 0):
  1286.         if not identifier:
  1287.             identifier = 0
  1288.         
  1289.         while self.trackers.has_key(identifier):
  1290.             identifier += 1
  1291.         self.trackers[identifier] = []
  1292.         return identifier
  1293.  
  1294.     create_tracker = simple_debug(create_tracker)
  1295.     
  1296.     def trackers_for_point(self, x, y, val = None):
  1297.         if val:
  1298.             track_for_point = (None, None, filter)((lambda t: (x, y, val) in t[1]), self.trackers.items())
  1299.         else:
  1300.             track_for_point = (None, filter)((lambda tkr: [] in [ t[1] == y for t in tkr[1] ]), self.trackers.items())
  1301.         return [ t[0] for t in track_for_point ]
  1302.  
  1303.     
  1304.     def get_tracker_color(self, identifier):
  1305.         if len(TRACKER_COLORS) > identifier:
  1306.             return TRACKER_COLORS[identifier]
  1307.         random_color = TRACKER_COLORS[0]
  1308.         while random_color in TRACKER_COLORS:
  1309.             random_color = (random.randint(0, 100) / 100, random.randint(0, 100) / 100, random.randint(0, 100) / 100)
  1310.             continue
  1311.             len(TRACKER_COLORS) > identifier
  1312.         TRACKER_COLORS.append(random_color)
  1313.         return self.get_tracker_color(identifier)
  1314.  
  1315.     
  1316.     def toggle_tracker(self, identifier, value):
  1317.         '''Toggle tracking for tracker identified by identifier.'''
  1318.         self.__trackers_tracking__[identifier] = value
  1319.  
  1320.     toggle_tracker = simple_debug(toggle_tracker)
  1321.     
  1322.     def delete_by_tracker(self, identifier):
  1323.         '''Delete all cells tracked by tracker ID identifer.'''
  1324.         ret = []
  1325.         while self.trackers[identifier]:
  1326.             (x, y, v) = self.trackers[identifier][0]
  1327.             ret.append((x, y, v, self.trackers_for_point(x, y, v)))
  1328.             self.remove(x, y)
  1329.             if self.grid and self.grid._get_(x, y):
  1330.                 self.grid.remove(x, y)
  1331.                 continue
  1332.         return ret
  1333.  
  1334.     
  1335.     def delete_except_for_tracker(self, identifier):
  1336.         tracks = self.trackers[identifier]
  1337.         removed = []
  1338.         for x in range(self.group_size):
  1339.             for y in range(self.group_size):
  1340.                 val = self.grid._get_(x, y)
  1341.                 if val and (x, y, val) not in tracks and not self.grid.virgin._get_(x, y):
  1342.                     removed.append((x, y, val, self.trackers_for_point(x, y, val)))
  1343.                     self.remove(x, y)
  1344.                     if self.grid and self.grid._get_(x, y):
  1345.                         self.grid.remove(x, y)
  1346.                     
  1347.                 self.grid._get_(x, y)
  1348.             
  1349.         
  1350.         return removed
  1351.  
  1352.     
  1353.     def add_tracker(self, x, y, tracker, val = None):
  1354.         self.__entries__[(x, y)].set_color(self.get_tracker_color(tracker))
  1355.         if not val:
  1356.             val = self.grid._get_(x, y)
  1357.         
  1358.         self.trackers[tracker].append((x, y, val))
  1359.  
  1360.     
  1361.     def remove_tracker(self, x, y, tracker, val = None):
  1362.         if not val:
  1363.             val = self.grid._get_(x, y)
  1364.         
  1365.         self.trackers[tracker].remove((x, y, val))
  1366.  
  1367.  
  1368.  
  1369. class GridDancer:
  1370.     DANCE_COLORS = [ colors.color_hex_to_float(hx) for hx in [
  1371.         '#cc0000',
  1372.         '#ef2929',
  1373.         '#f57900',
  1374.         '#fcaf3e',
  1375.         '#fce94f',
  1376.         '#8ae234',
  1377.         '#73d216',
  1378.         '#729fcf',
  1379.         '#3465a4',
  1380.         '#ad7fa8',
  1381.         '#75507b'] ]
  1382.     STEPS_PER_ANIMATION = 10
  1383.     
  1384.     def __init__(self, grid):
  1385.         self.animations = [
  1386.             self.value_dance,
  1387.             self.box_dance,
  1388.             self.col_dance,
  1389.             self.row_dance]
  1390.         self.current_animation = self.value_dance
  1391.         self.step = 0
  1392.         self.grid = grid
  1393.         self.dancing = False
  1394.         for box in self.grid.__entries__.values():
  1395.             if box.read_only == True:
  1396.                 box.read_only = False
  1397.                 box.read_only_hidden = True
  1398.             
  1399.             box.queue_draw()
  1400.         
  1401.  
  1402.     
  1403.     def start_dancing(self):
  1404.         for box in self.grid.__entries__.values():
  1405.             box.props.can_focus = False
  1406.         
  1407.         self.grid.get_toplevel().child_focus(gtk.DIR_TAB_BACKWARD)
  1408.         self.dancing = True
  1409.         gobject.timeout_add(350, self.dance_grid)
  1410.  
  1411.     
  1412.     def stop_dancing(self):
  1413.         self.dancing = False
  1414.         for box in self.grid.__entries__.values():
  1415.             box.props.can_focus = True
  1416.             if box.read_only_hidden == True:
  1417.                 box.read_only = True
  1418.                 box.read_only_hidden = False
  1419.                 continue
  1420.         
  1421.         self.grid.unhighlight_cells()
  1422.  
  1423.     
  1424.     def do_dance_step(self):
  1425.         self.grid.__entries__[(random.randint(0, 8), random.randint(0, 8))].set_background_color(random.choice(self.DANCE_COLORS))
  1426.  
  1427.     
  1428.     def rotate_animation(self):
  1429.         ci = self.animations.index(self.current_animation)
  1430.         if ci + 1 == len(self.animations):
  1431.             ni = 0
  1432.         else:
  1433.             ni = ci + 1
  1434.         self.current_animation = self.animations[ni]
  1435.         self.step = 0
  1436.  
  1437.     
  1438.     def dance_grid(self):
  1439.         if not self.dancing:
  1440.             return None
  1441.         if self.step > self.STEPS_PER_ANIMATION:
  1442.             self.rotate_animation()
  1443.         
  1444.         if hasattr(self, 'adjustment'):
  1445.             self.adjustment += 1
  1446.         else:
  1447.             self.adjustment = 0
  1448.         if self.adjustment >= 9:
  1449.             self.adjustment = 0
  1450.         
  1451.         
  1452.         try:
  1453.             self.current_animation()
  1454.         except AttributeError:
  1455.             return True
  1456.  
  1457.         self.step += 1
  1458.         if self.dancing:
  1459.             return True
  1460.  
  1461.     
  1462.     def col_dance(self):
  1463.         for x in range(9):
  1464.             n = (x + self.adjustment) % len(self.DANCE_COLORS)
  1465.             color = self.DANCE_COLORS[n]
  1466.             for y in range(9):
  1467.                 self.grid.__entries__[(x, y)].set_background_color(color)
  1468.             
  1469.         
  1470.  
  1471.     
  1472.     def row_dance(self):
  1473.         for y in range(9):
  1474.             n = (y + self.adjustment) % len(self.DANCE_COLORS)
  1475.             color = self.DANCE_COLORS[n]
  1476.             for x in range(9):
  1477.                 self.grid.__entries__[(x, y)].set_background_color(color)
  1478.             
  1479.         
  1480.  
  1481.     
  1482.     def box_dance(self):
  1483.         for box in range(9):
  1484.             n = (box + self.adjustment) % len(self.DANCE_COLORS)
  1485.             color = self.DANCE_COLORS[n]
  1486.             for x, y in self.grid.grid.box_coords[box]:
  1487.                 self.grid.__entries__[(x, y)].set_background_color(color)
  1488.             
  1489.         
  1490.  
  1491.     
  1492.     def value_dance(self):
  1493.         for value in range(10):
  1494.             n = (value + self.adjustment) % len(self.DANCE_COLORS)
  1495.             color = self.DANCE_COLORS[n]
  1496.             for x in range(9):
  1497.                 for y in range(9):
  1498.                     if self.grid.grid._get_(x, y) == value:
  1499.                         self.grid.__entries__[(x, y)].set_background_color(color)
  1500.                         continue
  1501.                 
  1502.             
  1503.         
  1504.  
  1505.  
  1506.  
  1507. def test_dance_grid(grid):
  1508.     dancer = GridDancer(grid)
  1509.     dancer.start_dancing()
  1510.     
  1511.     def stop(*args):
  1512.         dancer.stop_dancing()
  1513.  
  1514.     gobject.timeout_add(15000, stop)
  1515.  
  1516.